package com.ejiaoyi.api.aop;

import com.alibaba.fastjson.JSONObject;
import com.ejiaoyi.api.dto.Rest;
import com.ejiaoyi.api.service.impl.ApiAuthServiceImpl;
import com.ejiaoyi.api.service.impl.ApiLogServiceImpl;
import com.ejiaoyi.api.util.ApiUtil;
import com.ejiaoyi.common.annotation.ApiAuthentication;
import com.ejiaoyi.common.entity.ApiLog;
import com.ejiaoyi.common.enums.TimeFormatter;
import com.ejiaoyi.common.exception.CustomException;
import com.ejiaoyi.common.util.DateTimeUtil;
import com.ejiaoyi.common.util.DesUtil;
import com.ejiaoyi.common.util.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

/**
 * API AOP
 *
 * @author unknownChivalrous
 * @since 2020-03-17
 */
@Component
@Aspect
@Order(1)
public class ApiAspect {

    @Autowired
    private ApiLogServiceImpl apiLogService;

    @Autowired
    private ApiAuthServiceImpl apiAuthService;

    private final static String AUTH_ERROR_MSG = "{ code: '-1', msg: 'forbidden!'}";

    private final static String REPLAY_ERROR_MSG = "{ code: '-1', msg: 'forbidden replay!'}";

    private final static String TOKEN_ERROR_MSG = "{ code: '-1', msg: 'the token is not support to this auth information!'}";

    @Pointcut("@annotation(com.ejiaoyi.common.annotation.ApiAuthentication))")
    public void api() {

    }

    @AfterThrowing(pointcut = "api()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        System.out.println("EXCEPTION: " + e);
    }

    @Around("api()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object[] args = proceedingJoinPoint.getArgs();
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        Class<?> responseClass = method.getReturnType();
        ApiAuthentication apiAuthentication = method.getAnnotation(ApiAuthentication.class);

        String apiName = "";
        String platform = "";
        String apiKey = "";
        String methodName = proceedingJoinPoint.getSignature().getName();
        try {
            int authentication = apiAuthentication.authentication();
            apiName = apiAuthentication.apiName();
            if (authentication < 0 || StringUtils.isEmpty(apiName)) {
                throw new CustomException(AUTH_ERROR_MSG);
            }
            JSONObject json = JSONObject.parseObject(JSONObject.toJSONString(args[authentication]));
            if (json == null) {
                throw new CustomException(AUTH_ERROR_MSG);
            }
            platform = json.getString("platform");
            apiKey = json.getString("api_key");

            boolean authFlag = apiAuthService.authentication(apiName, platform, apiKey);

            if (!authFlag) {
                throw new CustomException(AUTH_ERROR_MSG);
            }

            if (apiAuthentication.replay()) {
                String token = json.getString("token");

                if (StringUtils.isEmpty(token)) {
                    throw new CustomException(REPLAY_ERROR_MSG);
                }

                if (RedisUtil.get(token) == null) {
                    throw new CustomException(TOKEN_ERROR_MSG);
                }

                String decrypt = DesUtil.decrypt(String.valueOf(RedisUtil.get(token)));
                String[] arr = StringUtils.split(decrypt, "-");
                if (StringUtils.equals(apiKey, arr[0]) || StringUtils.equals(platform, arr[1])) {
                    throw new CustomException(TOKEN_ERROR_MSG);
                }
            }

        } catch (CustomException e) {
            if (responseClass == Rest.class) {
                return ApiUtil.convertRest(e.getMessage());
            } else {
                return ApiUtil.convertRestObject(e.getMessage());
            }

        } catch (Throwable e) {
            e.printStackTrace();
            if (responseClass == Rest.class) {
                return ApiUtil.convertRest(AUTH_ERROR_MSG);
            } else {
                return ApiUtil.convertRestObject(AUTH_ERROR_MSG);
            }
        }

        String params = this.getParam(args);

        LocalDateTime createTime = LocalDateTime.parse(DateTimeUtil.getInternetTime(TimeFormatter.YYYY_HH_DD_HH_MM_SS), DateTimeFormatter.ofPattern(TimeFormatter.YYYY_HH_DD_HH_MM_SS.getCode(), Locale.CHINA));

        // 记录API请求日志
        ApiLog apiLog = ApiLog.builder()
                .apiName(apiName)
                .methodName(methodName)
                .params(params)
                .platform(platform)
                .apiKey(apiKey)
                .createApiTime(createTime)
                .build();

        Integer apiId = apiLogService.addLog(apiLog);

        if (apiId != -1) {
            String response = "";

            try {
                response = proceedingJoinPoint.proceed().toString();
            } catch (Throwable e) {
                e.printStackTrace();
            }

            LocalDateTime responseTime = LocalDateTime.parse(DateTimeUtil.getInternetTime(TimeFormatter.YYYY_HH_DD_HH_MM_SS), DateTimeFormatter.ofPattern(TimeFormatter.YYYY_HH_DD_HH_MM_SS.getCode(), Locale.CHINA));

            long ss = DateTimeUtil.getTimeDiff(createTime, responseTime);

            ApiLog log = ApiLog.builder()
                    .id(apiId)
                    .response(response)
                    .responseTime(responseTime)
                    .responseTimeConsume(ss)
                    .build();

            apiLogService.updateLog(log);
        }

        return proceedingJoinPoint.proceed();
    }

    /**
     * 获取参数
     *
     * @param args 参数列表
     * @return 参数内容字符串
     */
    private String getParam(Object[] args) {
        if (args.length == 0) {
            return "";
        }

        StringBuilder paramStr = new StringBuilder();
        Class<?> clazz;
        String paramName;

        for (Object arg : args) {
            clazz = arg.getClass();
            paramName = clazz.getName();
            if (clazz.isPrimitive() || arg instanceof String) {
                paramStr.append(paramName)
                        .append("=")
                        .append(arg);
            } else {
                paramStr.append(paramName)
                        .append("=")
                        .append(JSONObject.toJSONString(arg));
            }
        }

        return paramStr.toString();
    }
}
